import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.metrics import (classification_report, confusion_matrix, ConfusionMatrixDisplay, accuracy_score)
from sklearn.decomposition import PCA


# 设置中文显示
# 'SimHei' 是支持中文的字体，'axes.unicode_minus' 解决负号显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 1. 数据准备
# 加载鸢尾花数据集，包含150个样本，4个特征，3个类别
iris = datasets.load_iris()
X = iris.data  # 特征数据 (150个样本，4个特征：花萼长度、花萼宽度、花瓣长度、花瓣宽度)
y = iris.target  # 目标标签 (3个类别：0-Setosa, 1-Versicolor, 2-Virginica)
feature_names = ['花萼长度', '花萼宽度', '花瓣长度', '花瓣宽度']  # 中文特征名称，便于可视化
class_names = iris.target_names  # 类别名称：['setosa', 'versicolor', 'virginica']

# 划分训练集和测试集，比例为7:3
X_train, X_test, y_train, y_test = train_test_split(
    X, y,
    test_size=0.3, # test_size=0.3 表示30%数据作为测试集
    random_state=42, # random_state=42 固定随机种子，确保结果可复现
    stratify=y # stratify=y 保证训练集和测试集的类别分布与原始数据一致
)

# 2. 模型训练与优化
# 初始化基础随机森林模型，random_state=42 确保结果可复现
base_model = RandomForestClassifier(random_state=42)

# 定义参数网格，用于网格搜索优化模型
param_grid = {
    'n_estimators': [50, 100, 200], # n_estimators: 决策树的数量
    'max_depth': [3, 5, None], # max_depth: 树的最大深度，None表示无限制
    'min_samples_split': [2, 5], # min_samples_split: 节点分裂所需的最小样本数
    'min_samples_leaf': [1, 2], # min_samples_leaf: 叶节点所需的最小样本数
    'bootstrap': [True, False] # bootstrap: 是否使用有放回抽样（True为默认Bootstrap采样）
}

# 创建网格搜索对象
grid_search = GridSearchCV(
    estimator=base_model, # estimator=base_model: 使用基础模型
    param_grid=param_grid, # param_grid: 参数网格
    cv=5, # cv=5: 5折交叉验证
    n_jobs=-1, # n_jobs=-1: 使用所有CPU核心加速计算
    verbose=2, # verbose=2: 显示详细的网格搜索进度
    scoring='accuracy' # scoring='accuracy': 以准确率作为评估指标
)

# 执行网格搜索，找到最佳参数组合
grid_search.fit(X_train, y_train)

# 输出最佳参数组合
best_params = grid_search.best_params_
print("\n最佳参数组合：", best_params)

# 使用最佳参数创建最终模型
final_model = grid_search.best_estimator_

# 3. 模型评估
# 使用最终模型对训练集和测试集进行预测
y_train_pred = final_model.predict(X_train)  # 训练集预测结果
y_test_pred = final_model.predict(X_test)    # 测试集预测结果

# 计算训练集和测试集的准确率
train_acc = accuracy_score(y_train, y_train_pred)  # 训练集准确率
test_acc = accuracy_score(y_test, y_test_pred)     # 测试集准确率
print(f"\n训练集准确率：{train_acc:.2%}")
print(f"测试集准确率：{test_acc:.2%}")

# 生成详细的分类报告，包括精确率、召回率和F1分数
print("\n分类评估报告：")
print(classification_report(y_test, y_test_pred, target_names=class_names))

# 4. 可视化分析
# 可视化1：特征重要性
plt.figure(figsize=(10, 6))  # 设置图表大小
importances = final_model.feature_importances_  # 获取特征重要性得分
# 生成排序索引，降序排列
indices = np.argsort(importances)[::-1]
# 创建条形图，展示特征重要性
plt.bar(
    range(X.shape[1]),     # X轴位置（0到3，对应4个特征）
    importances[indices],  # 按重要性排序后的数据
    align="center",        # 条形居中对齐
    color='#1f77b4',       # 统一蓝色
    alpha=0.7             # 设置透明度为0.7
)
# 设置X轴刻度标签，使用排序后的特征名，旋转45度防止重叠
plt.xticks(
    range(X.shape[1]),
    np.array(feature_names)[indices],
    rotation=45,
    ha='right'
)
# 添加坐标轴标签和标题
plt.xlabel("特征名称", fontsize=12)
plt.ylabel("重要性得分", fontsize=12)
plt.title("鸢尾花分类特征重要性排序", fontsize=14, pad=15)

# 添加网格线，提升可读性
plt.grid(axis='y', linestyle='--', alpha=0.5)

# 优化布局，防止标签裁剪
plt.tight_layout()
plt.show()

# 可视化2：混淆矩阵
fig, ax = plt.subplots(figsize=(8, 6))  # 创建图表，设置大小
cm = confusion_matrix(y_test, y_test_pred)  # 计算混淆矩阵
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=class_names)  # 创建混淆矩阵显示对象
disp.plot(cmap=plt.cm.Blues, values_format="d", ax=ax)  # 绘制混淆矩阵，使用蓝色调，整数格式
plt.title("混淆矩阵")
plt.grid(False)  # 关闭网格线
plt.show()
plt.close(fig)  # 关闭图表，释放资源

# 可视化3：PCA降维决策边界
def plot_decision_boundary():
    # 使用PCA将4维特征降维到2维
    pca = PCA(n_components=2)
    X_pca = pca.fit_transform(X)  # 转换数据到2D

    # 训练一个简化模型，使用最佳参数
    model = RandomForestClassifier(**best_params, random_state=42)
    model.fit(X_pca, y)  # 在降维数据上训练

    # 创建网格数据，用于绘制决策边界
    x_min, x_max = X_pca[:, 0].min() - 1, X_pca[:, 0].max() + 1  # X轴范围
    y_min, y_max = X_pca[:, 1].min() - 1, X_pca[:, 1].max() + 1  # Y轴范围
    xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02),  # 生成网格
                         np.arange(y_min, y_max, 0.02))

    # 预测网格点类别
    Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)  # 重塑为网格形状

    # 绘制决策边界
    plt.figure(figsize=(12, 8))  # 设置图表大小
    plt.contourf(xx, yy, Z, alpha=0.4, cmap=plt.cm.Paired)  # 填充决策边界
    scatter = plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y, edgecolors='k', cmap=plt.cm.Paired)  # 绘制散点图
    plt.legend(*scatter.legend_elements(), title="类别")  # 添加图例
    plt.xlabel("主成分1")
    plt.ylabel("主成分2")
    plt.title("基于PCA降维的决策边界")
    plt.show()

plot_decision_boundary()  # 调用函数绘制决策边界